/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.jueb.sniff4j.socksCroe;
import java.net.InetSocketAddress;

import net.jueb.sniff4j.extr.auth.User;
import net.jueb.sniff4j.extr.base.AbstractRuntime;
import net.jueb.sniff4j.extr.sendHandler.SendToClientHandler;
import net.jueb.sniff4j.extr.sendHandler.SendToTargetHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.socks.SocksCmdRequest;
import io.netty.handler.codec.socks.SocksCmdResponse;
import io.netty.handler.codec.socks.SocksCmdStatus;
import io.netty.handler.codec.socks.SocksMessageEncoder;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

@ChannelHandler.Sharable
public final class SimpleSocksServerConnectHandler extends SimpleChannelInboundHandler<SocksCmdRequest> {
	protected static final InternalLogger log = InternalLoggerFactory.getInstance(SocksServerHandler.class);
	private static final String name = "SOCKS_SERVER_CONNECT_HANDLER";

    public static String getName() {
        return name;
    }
    
    @Override
    public void channelRead0(final ChannelHandlerContext proxyClientCtx, final SocksCmdRequest request) throws Exception {
    	//取出客户端请求的目标主机地址和端口，可能是IP可能是域名
    	String host=request.host();
    	int port=request.port();
    	/**
    	 * 代理服务器和客户端的通道
    	 */
    	final Channel proxyClientChannel=proxyClientCtx.channel();
    	final User user=AbstractRuntime.getRuntime(proxyClientChannel).getCurrentUser(proxyClientChannel);
    	
    	//TODO 进行代理目标拒绝认证判断
    	InetSocketAddress target=InetSocketAddress.createUnresolved(host,port);//客户端要访问的目标地址
    	boolean serverRefuse=AbstractRuntime.getRuntime(proxyClientChannel).getRefuseProxyTargets().contains(target);
    	boolean userRefuse=user.getRefuseTargets().contains(target);
    	if(serverRefuse || userRefuse)
    	{//如果有一个配置拒绝，则断开连接
    		//如果请求的代理目标是不允许的，则返回“规则不允许转发”错误
    		proxyClientChannel.writeAndFlush(new SocksCmdResponse(SocksCmdStatus.FORBIDDEN, request.addressType()));
            SocksServerUtils.closeOnFlush(proxyClientCtx.channel());
            return ;
    	}
    
    	//TODO 进行重定向判断
    	boolean serverRedirect=AbstractRuntime.getRuntime(proxyClientChannel).getTargetRedirects().containsKey(target);
    	if(serverRedirect)
    	{//如果服务器要对客户端访问的目标地址进行强制重定向
    		InetSocketAddress newAddress=AbstractRuntime.getRuntime(proxyClientChannel).getTargetRedirects().get(target);
    		if(newAddress!=null)
    		{
    			host=newAddress.getHostString();
    			port=request.port();
    			log.debug(user.getName()+"{目标被重定向["+request.host()+":"+request.port()+"==>"+host+":"+port+"]}");
    		}
    	}else
    	{//如果服务器未处理，判断用户是否使用规则
    		boolean userRedirect=AbstractRuntime.getRuntime(proxyClientChannel).getTargetRedirects().containsKey(target);
    		if(userRedirect)
        	{//如果服务器要对客户端访问的目标地址进行强制重定向
        		InetSocketAddress newAddress=user.getTargetRedirects().get(target);
        		if(newAddress!=null)
        		{
        			host=newAddress.getHostString();
        			port=request.port();
        			log.debug(user.getName()+"{目标被重定向["+request.host()+":"+request.port()+"==>"+host+":"+port+"]}");
        		}
        	}
    	}
    	
    	
    	//********************执行代理连接过程*********************
    	
        //创建连接到客户端请求的目标主机管道
        final Bootstrap serverClient = new Bootstrap();
        EventLoop ep=proxyClientChannel.eventLoop();
        //NIO:NioEventLoop OIO:ThreadPerChannelEventLoop
        if(ep instanceof NioEventLoop)
        {//如果是NIO则用服务端的线程
        	serverClient.group(ep);
        }else
        {//如果服务端是OIO就自己建立
        	serverClient.group(new NioEventLoopGroup());
        }
        serverClient.channel(NioSocketChannel.class);
        serverClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
         .option(ChannelOption.SO_KEEPALIVE, true)//设置到目标主机不保持长连接
         //添加初始化对象
        .handler(new ChannelInitializer<SocketChannel>()
        		 {
					@Override
					protected void initChannel(SocketChannel ch)throws Exception {
						//这里暂时不需要任何初始化操作
					}
        		 });
        //连接代理客户端要访问的目标地址和端口,并添加侦听事件
        serverClient.connect(host,port).addListener(new ChannelFutureListener() {
            
        	/**
        	 * 此方法优先于promise的Listener方法
        	 * 基于事件触发后续操作
        	 * @param future
        	 * @throws Exception
        	 */
            public void operationComplete(ChannelFuture future) throws Exception {
            	/**
            	 * 代理服务器和目标主机的通道
            	 */
                final Channel proxyTargeChannel = future.channel();
                if (future.isSuccess() && proxyTargeChannel.isActive()) 
                {//如果连接成功,向socks客户端返回超过信息
                	ChannelFuture cf=proxyClientChannel.writeAndFlush(new SocksCmdResponse(SocksCmdStatus.SUCCESS, request.addressType()));
                    //连接成功的信息反馈给socks客户端后就开始进行数据交换
                	cf.addListener(new ChannelFutureListener(){
                    	/**
                    	 * 当向客户端报告成功连接目标主机后执行以下操作
                    	 * @param channelFuture
                    	 * @throws Exception
                    	 */
                        public void operationComplete(ChannelFuture channelFuture) throws Exception 
                        {//当回复成功状态后，边移除当前handler，并加入新handler进行转发操作
                        	log.debug(user.getName()+"{代理目标:"+request.host()+":"+request.port()+"建立连接成功！}");
                    		proxyClientCtx.pipeline().remove(SimpleSocksServerConnectHandler.getName());
                            //如果连接成功，则不需要返回socks信息了，去掉socks消息编码器
                    		proxyClientChannel.pipeline().remove(SocksMessageEncoder.getName());
                    		
                    		//TODO 在代理管道加入runtime,因为proxyClientChannel的父管道就是server总管道，在启动server的时候被绑定runtime，即子管道也相当于存在runtime，随时可以拿到
                    		AbstractRuntime rt=AbstractRuntime.getRuntime(proxyClientChannel);
                    		rt.bindChannel(proxyTargeChannel);
                    		
                    		// TODO 在代理管道加入客户端通道登记的用户上下文,使得客户端通道和客户端使用的代理通道保持上下文一致
                    		rt.initChannelContext(proxyTargeChannel,rt.getChannelContext(proxyClientChannel));
                    		/**
                    		 * System.out.println("代理服务器到目标主机的handler数量："+proxyTargeChannel.pipeline().toMap().size()+":"+proxyTargeChannel.pipeline().toMap().keySet().toString());
                    		 * System.out.println("代理服务器到客户端的handler数量："+proxyClientChannel.pipeline().toMap().size()+":"+proxyClientChannel.pipeline().toMap().keySet().toString());
                    		 */
                    		/**
                    		 * 初始化代理成功后的2个通道的编码解码配置
                    		 */
                    		rt.getChannelCodecInit().codecInit(proxyClientChannel, proxyTargeChannel);
                    		
                    		/**
                    		 * 在代理服务器与目标主机管道加入转发器，
                    		 * 转发器用于将目标数据返回给客户端
                    		 */
                    		proxyTargeChannel.pipeline().addLast(SendToClientHandler.class.getName(),new SendToClientHandler(proxyClientChannel,rt));
                    		
                    		/**
                    		 * 在代理服务器与客户端管道加入转发器，
                    		 * 转发器用于将客户端数据发送给目标主机
                    		 */
                    		proxyClientChannel.pipeline().addLast(SendToTargetHandler.class.getName(),new SendToTargetHandler(proxyTargeChannel,rt));
                        }
                    }); 
                } else 
                {//如果连接失败，则返回失败信息并关闭连接
                	log.warn(user.getName()+"{代理目标:"+request.host()+":"+request.port()+"建立连接失败！}");
                	proxyClientChannel.writeAndFlush(new SocksCmdResponse(SocksCmdStatus.FAILURE, request.addressType()));
                    SocksServerUtils.closeOnFlush(proxyClientCtx.channel());
                }
            }
        });
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        SocksServerUtils.closeOnFlush(ctx.channel());
    }
}
